package com.amadeus.spin.devtool.soap.shooter.process;

import java.io.IOException;
import java.io.StringReader;
import java.util.Stack;

import org.apache.log4j.Logger;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;
import org.xml.sax.helpers.XMLReaderFactory;

import com.amadeus.spin.devtool.soap.shooter.FatalException;
import com.amadeus.spin.devtool.soap.shooter.ProcessInput;
import com.amadeus.spin.devtool.soap.shooter.ProcessOutput;
import com.amadeus.spin.devtool.soap.shooter.config.ConfigurationParameters;
import com.amadeus.spin.devtool.soap.shooter.config.SoapshooterScenario;
import com.amadeus.spin.standard.util.Dom4jUtils;
import com.amadeus.spin.standard.util.XPathHelper;

public class ValidateGrammar extends DefaultHandler implements ProcessInput,
    ProcessOutput {
  private static final String IS_STRUCTURED_XML = "<IS_STRUCTURED_XML>%s</IS_STRUCTURED_XML>";

  private static final String IS_STRUCTURED_XML_YES = String.format(IS_STRUCTURED_XML, "YES");

  private static final String IS_STRUCTURED_XML_TRUE = String.format(IS_STRUCTURED_XML, "TRUE");

  private static final String IS_STRUCTURED_XML_NO = String.format(IS_STRUCTURED_XML, "NO");

  private static final String IS_STRUCTURED_XML_FALSE = String.format(IS_STRUCTURED_XML, "FALSE");

  private static final String FAILED_MESSAGE = "Failed.";

  private static final String TEST_GOES_ON_MESSAGE = " The test goes on since fatal=\"false\"";

  private static final String SCHEMA_VALIDATION_FAILED = "Schema validation failed.";

  private static final String SEE_VERBOSE_MESSAGE = " See verbose mode (--verbose) for details.";

  /**
   * WARNING: logger not initialized until {@link #validateGrammar(String, SoapshooterScenario)} is called!
   */
  private static Logger logger = Logger.getLogger(ValidateGrammar.class);

  private boolean m_validationError = false;

  private boolean m_validationIsFatal = false;

  /**
   * @see org.xml.sax.helpers.DefaultHandler#resolveEntity(java.lang.String, java.lang.String)
   */
  @Override
  public InputSource resolveEntity(String publicId, String systemId) {
    return null;
  }

  /**
   * Legacy method to retrieve root node name. Modified it so that we use
   * Document instead of string parsing. TODO : check the purpose of his method,
   * move its business accordingly.
   *
   * @param inputXml
   * @return
   */
  protected static String getNodeNameFromInputXml(String inputXml) {
    String rootNodeName = "Not Found.";
    try {
      Node node = Dom4jUtils.createDOMnode(inputXml);
      if (node != null) {
        rootNodeName = node.getNodeName();
      }
    }
    catch (FatalException e) {
      logger.error(e.getMessage());
    }
    return rootNodeName;
  }

  /**
   * @see com.amadeus.spin.devtool.soap.shooter.ProcessInput#processInput(java.lang .String, java.lang.String,
   *      org.w3c.dom.Node, com.amadeus.spin.devtool.soap.shooter.logging.LogStrategy)
   */
  @Override
  public String processInput(Stack<String> previousOutput, String input, Node config) throws FatalException {
    boolean ignoreOverride = false;
    String rootNodeName = getNodeNameFromInputXml(input);
    if (ignoreOverride && "OverrideInput".equalsIgnoreCase(rootNodeName)) {
      logger.info("skipping, since type of request is '" + rootNodeName + "' (no updated schema available).");
    }
    else if (isStructuredXml(input)) {
      validateGrammar(input, XPathHelper.getProperty(config, "@fatal"));
    }
    return input;
  }

  /**
   * Checks weither the XML is considered as a structured XML
   *
   * @param input
   * @return
   */
  protected boolean isStructuredXml(String input) {
    if (input.toUpperCase().contains(IS_STRUCTURED_XML_YES) ||
        input.toUpperCase().contains(IS_STRUCTURED_XML_TRUE)) {
      return true;
    }
    if (input.toUpperCase().contains(IS_STRUCTURED_XML_NO) || input.toUpperCase().contains(IS_STRUCTURED_XML_FALSE)) {
      return false;
    }
    // Disable the xsd lookup when nothing defined in schema until explicitely asked by Testing teams.
    // return !xsdHelper.isElligibleToStructuredTag(xsdHelper.getXsdFromXml(input));
    return true;
  }

  /**
   * (non-Javadoc)
   *
   * @see com.amadeus.spin.devtool.soap.shooter.ProcessOutput#processOutput(java.lang .String, org.w3c.dom.Node,
   *      com.amadeus.spin.util.soapshooter.Shooter, com.amadeus.spin.devtool.soap.shooter.logging.LogStrategy)
   **/
  @Override
  public void processOutput(String output, Node config,
      com.amadeus.spin.util.soapshooter.Shooter shooter) throws FatalException {
    String rootNodeName = getNodeNameFromInputXml(output);
    if ("OverrideOutput".equalsIgnoreCase(rootNodeName)) {
      logger.info("skipping, since type of request is '" + rootNodeName + "' (no updated schema available).");
    }
    else {
      validateGrammar(output, XPathHelper.getProperty(config, "@fatal"));
    }
  }

  /**
   * Maybe impacted by this bug : https://issues.apache.org/jira/browse/XERCESJ-1130
   *
   * @param input
   * @param conf
   * @return
   * @throws FatalException
   */
  protected boolean validateGrammar(String input, String sfatal) throws FatalException {

    m_validationError = false;
    m_validationIsFatal = !"false".equals(sfatal);

    if (input.contains("xsi:noNamespaceSchemaLocation")) {
      try {

        String validationFeature = "http://xml.org/sax/features/validation";
        String schemaFeature = "http://apache.org/xml/features/validation/schema";
        String schemaFullChecking = "http://apache.org/xml/features/validation/schema-full-checking";

        XMLReader reader = XMLReaderFactory.createXMLReader();
        reader.setErrorHandler(this);
        StringReader stringReader = new StringReader(input);
        InputSource inputSource = new InputSource(stringReader);
        reader.setFeature(validationFeature, true);
        reader.setFeature(schemaFeature, true);
        reader.setFeature(schemaFullChecking, true);
        reader.setEntityResolver(this);
        reader.setErrorHandler(this);
        reader.parse(inputSource);
        if (m_validationError) {
          onValidationError(null);
        }
        else {
          logger.info("OK - document is valid against its XSD.");
        }
      }
      catch (SAXException e) {
        onValidationError(e);
      }
      catch (IOException e) {
        onValidationError(e);
      }
    }
    else {
      logger.warn("No xsd defined in the xml content - no grammar validation performed.");
    }
    return m_validationError;
  }

  /**
   * Prints a warning to tell the user that the validation failed
   *
   * @throws FatalException
   */
  private void onValidationError(Exception e) throws FatalException {

    String errorMsg = FAILED_MESSAGE;
    if (!ConfigurationParameters.isVerbose()) {
      errorMsg += SEE_VERBOSE_MESSAGE;
    }
    else if (e != null && e.getMessage() != null) {
      errorMsg += e.getMessage();
      logger.error(errorMsg, e);
    }
    if (m_validationIsFatal) {
      logger.error(errorMsg);
      throw new FatalException(SCHEMA_VALIDATION_FAILED);
    }
    errorMsg += TEST_GOES_ON_MESSAGE;
    logger.warn(errorMsg);
  }

  /**
   *
   * @param ex
   */
  private void logParsingErrorIfUseful(SAXParseException ex) {
    String message = ex.getMessage();
    m_validationError = true;
    if (ConfigurationParameters.isVerbose()) {
      if (m_validationIsFatal) {
        logger.error(message);
      }
      else {
        logger.warn(message);
      }
    }
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void fatalError(SAXParseException ex) throws org.xml.sax.SAXException {
    logParsingErrorIfUseful(ex);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void error(SAXParseException ex) throws org.xml.sax.SAXException {
    logParsingErrorIfUseful(ex);
  }

  /**
   * {@inheritDoc}
   */
  @Override
  public void warning(SAXParseException ex) throws org.xml.sax.SAXException {
    super.warning(ex);
  }
}